﻿using System.Linq;
using Duality;
using Duality.Components;
using Duality.Components.Renderers;
using Duality.Resources;
using OpenTK;

namespace ClientApp
{
	public enum WarriorCommand
	{
		Attack,
		Hold,
		Retreat
	}
	public static class ExtMethodsWarriorCommand
	{
		public static ContentRef<Material> GetIconMaterial(this WarriorCommand cmd)
		{
			switch (cmd)
			{
				case WarriorCommand.Attack: return GameRes.Data.Gfx.sword_Material;
				case WarriorCommand.Hold: return GameRes.Data.Gfx.shield_Material;
				case WarriorCommand.Retreat: return GameRes.Data.Gfx.whiteflag_Material;
			}
			return Material.DualityLogo256;
		}
	}

	[RequiredComponent(typeof(Transform))]
	[RequiredComponent(typeof(Agent))]
	[RequiredComponent(typeof(SpriteRenderer))]
	public class Warrior : Creature
	{
		private const float TargetRange = 300.0f;
		private const float AttackRange = 15.0f;
		private const float AttackDuration = 750.0f;

		private DamageType weaponType = DamageType.Rock;
		private float attackAnimState = 0.0f;
		private bool attackLaunched = false;
		private Creature attackTarget = null;
		private Rect attackRectBackup = Rect.Empty;
		private bool attackDamageDealt = false;
		private Vector2 visualAngleAccum = Vector2.Zero;

		public DamageType WeaponType
		{
			get { return this.weaponType; }
			set { this.weaponType = value; }
		}

		public override float PhysicalBoundRad
		{
			get { return 25.0f; }
		}

		public override void OnUpdate()
		{
			base.OnUpdate();

			// Retrieve general data
			Vector2 targetVisualAngleDir = this.visualAngleAccum;
			Transform transform = this.GameObj.Transform;
			SpriteRenderer renderer = this.GameObj.Renderer as SpriteRenderer;
			Agent agent = this.GameObj.GetComponent<Agent>();
			PlayerIdentity enemyPlayerId = this.PlayerId.Opposing();
			Player enemyPlayer = enemyPlayerId.GetPlayer();
			Headquarter enemyHq = enemyPlayer == null ? null : enemyPlayer.Hq;
			Player myPlayer = this.PlayerId.GetPlayer();
			Headquarter myHq = myPlayer == null ? null : myPlayer.Hq;

			agent.Hrvo.IgnoredAgents.Clear();
			// If an attack is in progress, don't do AI stuff, just finish the attack
			if (this.attackLaunched)
			{
				if (!this.attackTarget.Disposed)
				{
					Vector2 enemyDir = (this.attackTarget.GameObj.Transform.Pos - transform.Pos).Xy;
					enemyDir.Normalize();
					targetVisualAngleDir = enemyDir;
				}

				this.attackAnimState += Time.MsPFMult * Time.TimeMult / AttackDuration;

				float localStateTemp = this.attackAnimState;
				if (this.attackAnimState < 0.05f)
				{
					localStateTemp /= 0.05f;
					renderer.Rect = this.attackRectBackup.Offset(0.0f, localStateTemp * -20.0f);
				}
				else if (this.attackAnimState < 0.5f)
				{
					if (!this.attackDamageDealt && this.attackTarget != null && !this.attackTarget.Disposed)
					{
						this.attackDamageDealt = true;
						this.attackTarget.DoDamage(0.25f, this.weaponType);

						Vector3 bamAnimTargetPos = (transform.Pos + this.attackTarget.GameObj.Transform.Pos) * 0.5f;

						// Check if there is already a BAM anim in radius
						var activeBamAnimArray = Scene.Current.Graph.ActiveObjects.GetComponents<BamAnim>().Where(ba => ba.Progress < 0.75f).ToArray();
						float nearestBamAnimDist =
							activeBamAnimArray.Length > 0
							? activeBamAnimArray.Min(ba => (ba.GameObj.Transform.Pos - bamAnimTargetPos).LengthFast)
							: 100000000;

						// If not, create one
						if (nearestBamAnimDist > 32)
						{
							var bamAnim = GameRes.Data.Gamedata.BamAnim_Prefab.Res.Instantiate();
							bamAnim.Transform.Pos = bamAnimTargetPos - Vector3.UnitZ;
							bamAnim.Transform.Vel = -Vector3.UnitZ * 0.25f;
							bamAnim.Transform.Angle = MathF.Rnd.NextFloat(0.0f, MathF.RadAngle360);
							bamAnim.GetComponent<BamAnim>().BaseSize = 48;
							Scene.Current.Graph.RegisterObjDeep(bamAnim);
						}
					}
					localStateTemp -= 0.05f;
					localStateTemp /= 0.5f - 0.05f;
					renderer.Rect = this.attackRectBackup.Offset(0.0f, (1.0f - localStateTemp) * -20.0f);
				}
				else
				{
					localStateTemp -= 0.05f + 0.5f;
					localStateTemp /= 1.0f - (0.5f + 0.05f);
				}
				if (this.attackAnimState >= 1.0f) this.EndAttack();
			}
			// Not attacking? Do AI calculus
			else
			{
				// Determine nearest enemy in range
				Creature nearestEnemy = Creature.Instances
					.Where(c => c.PlayerId == enemyPlayerId)
					.OrderBy(c => ScoreEnemyWarrior(c))
					.FirstOrDefault();
				float nearestEnemyDist = float.PositiveInfinity;
				if (nearestEnemy != null)
				{
					nearestEnemyDist = (transform.Pos - nearestEnemy.GameObj.Transform.Pos).Length;
					nearestEnemyDist -= this.PhysicalBoundRad;
					nearestEnemyDist -= nearestEnemy.PhysicalBoundRad;
				}
				if (nearestEnemy != null && nearestEnemyDist > TargetRange)
					nearestEnemy = null;

				const float preferedVelocity = 1.5f;
				WarriorCommand command = WarriorCommand.Attack;
				if (this.WeaponType == DamageType.Rock) command = this.PlayerId.GetPlayer().CurrentCmdRock;
				else if (this.WeaponType == DamageType.Paper) command = this.PlayerId.GetPlayer().CurrentCmdPaper;
				else if (this.WeaponType == DamageType.Scissor) command = this.PlayerId.GetPlayer().CurrentCmdScissor;

				bool isNearMyHq = myHq != null && !myHq.Disposed && (transform.Pos - myHq.GameObj.Transform.Pos).Length < 300;
				bool isVeryNearMyHq = myHq != null && !myHq.Disposed && (transform.Pos - myHq.GameObj.Transform.Pos).Length < 150;

				if (command == WarriorCommand.Attack)
				{
					// If there's an enemy, target it
					if (nearestEnemy != null)
					{
						Agent enemyAgent = nearestEnemy.GameObj.GetComponent<Agent>();
						if (enemyAgent != null)
							agent.Hrvo.IgnoredAgents.Add(enemyAgent);

						Vector2 enemyDir = (nearestEnemy.GameObj.Transform.Pos - transform.Pos).Xy;
						enemyDir.Normalize();
						enemyDir *= preferedVelocity;

						float enemyAngle = MathF.Angle(enemyDir.X, enemyDir.Y);
						if (nearestEnemyDist < AttackRange)
						{
							targetVisualAngleDir = enemyDir;
							agent.PreferedVelocity = Vector2.Zero;
							if (MathF.CircularDist(transform.Angle, enemyAngle) < MathF.RadAngle30 * 0.5f)
							{
								if (!this.attackLaunched)
									this.LaunchAttack(nearestEnemy);
							}
						}
						else
						{
							agent.PreferedVelocity = enemyDir;
							targetVisualAngleDir = transform.Vel.Xy;
						}
					}
					// Otherwise, move to enemy Hq
					else if (enemyHq != null)
					{
						Vector2 enemyHqDir = (enemyHq.GameObj.Transform.Pos - transform.Pos).Xy;
						enemyHqDir.Normalize();
						enemyHqDir *= preferedVelocity;

						agent.PreferedVelocity = enemyHqDir;
						targetVisualAngleDir = transform.Vel.Xy;
					}
				}
				else if (command == WarriorCommand.Hold)
				{
					// If there's an enemy, target it
					if (nearestEnemy != null)
					{
						Agent enemyAgent = nearestEnemy.GameObj.GetComponent<Agent>();
						if (enemyAgent != null)
							agent.Hrvo.IgnoredAgents.Add(enemyAgent);

						Vector2 enemyDir = (nearestEnemy.GameObj.Transform.Pos - transform.Pos).Xy;
						enemyDir.Normalize();
						enemyDir *= preferedVelocity;

						float enemyAngle = MathF.Angle(enemyDir.X, enemyDir.Y);
						if (nearestEnemyDist < AttackRange)
						{
							targetVisualAngleDir = enemyDir;
							agent.PreferedVelocity = Vector2.Zero;
							if (MathF.CircularDist(transform.Angle, enemyAngle) < MathF.RadAngle30 * 0.5f)
							{
								if (!this.attackLaunched)
									this.LaunchAttack(nearestEnemy);
							}
						}
						else
						{
							agent.PreferedVelocity = enemyDir;
							targetVisualAngleDir = transform.Vel.Xy;
						}
					}
					// Otherwise, stop
					else
					{
						agent.PreferedVelocity = Vector2.Zero;
					}
				}
				else if (command == WarriorCommand.Retreat)
				{
					// If there's an enemy, run from it, except if near hq
					if (nearestEnemy != null)
					{
						Vector2 enemyDir = (nearestEnemy.GameObj.Transform.Pos - transform.Pos).Xy;
						enemyDir.Normalize();
						enemyDir *= preferedVelocity;

						if (isNearMyHq)
						{
							Agent enemyAgent = nearestEnemy.GameObj.GetComponent<Agent>();
							if (enemyAgent != null)
								agent.Hrvo.IgnoredAgents.Add(enemyAgent);

							float enemyAngle = MathF.Angle(enemyDir.X, enemyDir.Y);
							if (nearestEnemyDist < AttackRange)
							{
								targetVisualAngleDir = enemyDir;
								agent.PreferedVelocity = Vector2.Zero;
								if (MathF.CircularDist(transform.Angle, enemyAngle) < MathF.RadAngle30 * 0.5f)
								{
									if (!this.attackLaunched)
										this.LaunchAttack(nearestEnemy);
								}
							}
							else
							{
								agent.PreferedVelocity = enemyDir;
								targetVisualAngleDir = transform.Vel.Xy;
							}
						}
						else
						{
							agent.PreferedVelocity = -enemyDir;
							targetVisualAngleDir = transform.Vel.Xy;
						}
					}
					// Otherwise, move to parent Hq
					else if (myHq != null)
					{
						Vector2 myHqDir = (myHq.GameObj.Transform.Pos - transform.Pos).Xy;
						myHqDir.Normalize();
						myHqDir *= preferedVelocity;

						if (isVeryNearMyHq)
						{
							agent.PreferedVelocity = Vector2.Zero;
						}
						else
						{
							agent.PreferedVelocity = myHqDir;
							targetVisualAngleDir = transform.Vel.Xy;
						}
					}
				}
			}

			if (targetVisualAngleDir.LengthSquared > 1.0f) targetVisualAngleDir.Normalize();
			this.visualAngleAccum += (targetVisualAngleDir - this.visualAngleAccum) * MathF.Pow(0.5f, Time.TimeMult);
			if (this.visualAngleAccum.Length > 0.00001f) transform.Angle = MathF.Angle(this.visualAngleAccum.X, this.visualAngleAccum.Y);
		}

		private void LaunchAttack(Creature target)
		{
			SpriteRenderer renderer = this.GameObj.Renderer as SpriteRenderer;

			this.attackTarget = target;
			this.attackLaunched = true;
			this.attackAnimState = 0.0f;
			this.attackRectBackup = renderer.Rect;
			this.attackDamageDealt = false;
		}
		private void EndAttack()
		{
			SpriteRenderer renderer = this.GameObj.Renderer as SpriteRenderer;
			renderer.Rect = this.attackRectBackup;

			this.attackTarget = null;
			this.attackLaunched = false;
			this.attackAnimState = 0.0f;
			this.attackRectBackup = Rect.Empty;
			this.attackDamageDealt = false;
		}
		private void PerformAttack()
		{

		}

		public override void Die()
		{
			base.Die();
			// create explosion
			GameObject bamAnim = GameRes.Data.Gamedata.BamAnim_Prefab.Res.Instantiate();
			bamAnim.Transform.Pos = this.GameObj.Transform.Pos - Vector3.UnitZ;
			bamAnim.Transform.Vel = -Vector3.UnitZ * 0.25f;
			bamAnim.Transform.Angle = MathF.Rnd.NextFloat(0.0f, MathF.RadAngle360);
			bamAnim.GetComponent<BamAnim>().BaseSize = 64;
			Scene.Current.Graph.RegisterObjDeep(bamAnim);
			// create splatter
			GameObject splatter = GameRes.Data.Gamedata.Splatter_Prefab.Res.Instantiate();
			splatter.Transform.Pos = this.GameObj.Transform.Pos + Vector3.UnitZ;
			splatter.GetComponent<Splatter>().BaseSize = 64;
			splatter.GetComponent<AnimSpriteRenderer>().ColorTint = this.PlayerId.GetUnitColor();
			Scene.Current.Graph.RegisterObjDeep(splatter);
		}

		protected float ScoreEnemyWarrior(Creature creature)
		{
			Warrior warrior = creature.GameObj.GetComponent<Warrior>();
			float distance = (this.GameObj.Transform.Pos - creature.GameObj.Transform.Pos).LengthSquared;
			if (distance > TargetRange * TargetRange)
				distance = float.MaxValue;

			float typeModifier = 1.0f;
			if (warrior != null)
			{
				if (this.ArmorType == ArmorType.Rock)
				{
					if (warrior.ArmorType == ArmorType.Scissor)
						typeModifier = 1f;
					else if (warrior.ArmorType == ArmorType.Paper)
						typeModifier = -1f;
				}
				else if (this.ArmorType == ArmorType.Paper)
				{
					if (warrior.ArmorType == ArmorType.Rock)
						typeModifier = 1f;
					else if (warrior.ArmorType == ArmorType.Scissor)
						typeModifier = -1f;
				}
				else if (this.ArmorType == ArmorType.Scissor)
				{
					if (warrior.ArmorType == ArmorType.Paper)
						typeModifier = 1f;
					else if (warrior.ArmorType == ArmorType.Rock)
						typeModifier = -1f;
				}
			}
			return distance - 50.0f * typeModifier;
		}
	}
}
